﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace OrangePrj
{
    class Modelling
    {
        private WorkList original;
        private WorkList stohastic;
        public WorkList determ;
        private WorkList currWorkSet = new WorkList();
        private List<WorkGroup> groups = new List<WorkGroup>();
        private Random rnd = new Random();
        private double resMax;
        private double error;
        public int Iterations { get; set; }

        private List<double> segments = new List<double>();
        private List<double> timeDist = new List<double>();

        public delegate void ProgressEventhandler(int value);
        public event ProgressEventhandler Onchanged;

        private class WorkGroup
        {
            public WorkType workType { get; set; }
            public WorkList works { get; set; }
            public List<int> depInd { get; set; }
            
            public WorkGroup(WorkType type, WorkList worklist)
            {
                this.workType = type;
                this.works = worklist;
                try
                {
                    this.depInd = worklist[0].DepInd;
                }
                catch (ArgumentOutOfRangeException e)
                {
                }
            }
            public WorkGroup()
            {
                works = new WorkList();
            }
            public WorkGroup(Work work):this()
            {
                works.Add(work);
                workType = work.Type;
                depInd = work.DepInd;
            }
        }

        public Modelling(WorkList orig)
        {
            this.original = (WorkList)orig.Clone();
            this.stohastic = (WorkList)original.Clone();
        }
        public void PlanWithRes()
        {
            CalcTime(determ);
            CalcSegments();
            int ind=0;
            while ((ind = RunCalcOrderOnSegment(ind)) != segments.Count - 1)
            {
 
            }
        }
        private int RunCalcOrderOnSegment(int ind)
        {
            if (CalcOrder(ind) == false)
            {
                CalcTime(determ);
                CalcSegments();
                return ind;
            }
            else
                return ind + 1;
        }
        public void SetParams(double res, double error, int Ntest)
        {
            this.resMax = res;
            this.error = error;
            this.Iterations = Ntest;
        }
        private void CalcTime(WorkList wlist)
        {
            for (int i = 0; i <= wlist.getMaxRank(); i++)
                foreach (Work w in wlist)
                    if (w.rank == i)
                    {
                        if (w.timestart < w.getMaxEndtime4Depend())
                            w.timestart = w.getMaxEndtime4Depend();
                        w.CalcEndTime();
                    }
        }
        private void CalcSegments()
        {
            segments.Clear();
            foreach (Work w in determ)
            {
                segments.Add(w.timestart);
                segments.Add(w.timeend);
            }
            RemoveDuplex(segments);
            segments.Sort();
        }

        public int CritSortRule(double i, double j)
        {
            Work w1 = this.original.getWorkByID((int)i);
            Work w2 = this.original.getWorkByID((int)j);
            if (w1.rank < w2.rank)
                return -1;
            else
                if (w1.rank == w2.rank)
                {
                    if (w1.timestart < w2.timestart)
                        return -1;
                    else
                        if (w1.timestart == w2.timestart)
                        {
                            if (w1.id < w2.id)
                                if (w2.Type == WorkType.Возврат)
                                    return 1;
                                else
                                    return -1;
                            else
                                return 0;
                        }
                        else
                            return 1;
                }
                else
                    return 1;
        }

        private void RemoveDuplex(List<double> list)
        {
            bool stat = true;
            while (stat == true)
            {
                foreach (double d1 in list)
                {
                    int count = 0;
                    foreach (double d2 in list)
                    {
                        if (d1 == d2)
                            count++;
                    }
                    for (int i = 1; i < count; i++)
                    {
                        list.Remove(d1);
                    }
                    if (count>1)
                        break;
                    if (list[list.Count - 1] == d1)
                        stat = false;
                }
            }
        }
        private bool CalcOrder(int ind)
        {
            WorkList list = new WorkList();
            //наполнение списка работ которые лежат в ind сегменте
            foreach (Work w in determ)
                if (w.timestart <= segments[ind])
                    if (w.timeend >= segments[ind + 1])
                        list.Add(w);
            //подсчет ресурсов которые необходимы работам сегмента
            double res=0;
            foreach (Work w in list)
                res += w.Resource.Amount;
            if (res <= resMax)
                return true;
            //при нехватке ресурсов необходимо вычислить какую работу сдвинуть
            else
            {
                int r = 1;
                foreach (Work w in list)
                    if (w.timestart < segments[ind])
                        w.ResID = r++;
                //вычисление резервов времени для каждой работы
                List<double>fullreserv = new List<double>();
                foreach (Work w in list)
                    if (w.ResID == 0)
                    {
                        double rd = w.getFullReserve();
                        if (rd == -1)
                            rd = determ.getPrjTime() - w.timeend;
                        fullreserv.Add(rd);
                    }
                fullreserv.Sort();
                foreach (double d in fullreserv)
                {
                    int c=0;
                    foreach (double d2 in fullreserv)
                        if (d2 == d)
                            c++;
                    if (c == 1)
                    {
                        foreach (Work w in list)
                            if (w.ResID == 0)
                            {
                                double rd = w.getFullReserve();
                                if (rd == -1)
                                    rd = determ.getPrjTime() - w.timeend;
                                if (rd == d)
                                    w.ResID = r++;
                            }
                    }
                    else
                    {
                        List<double> resIntens = new List<double>();
                        foreach (Work w in list)
                        {
                            double rd = w.getFullReserve();
                            if (rd == -1)
                                rd = determ.getPrjTime() - w.timeend;
                            if (rd == d)
                                resIntens.Add(w.Resource.Amount);
                        }
                        resIntens.Sort();
                        for (int k=resIntens.Count-1; k>=0; k--)
                            foreach (Work w in list)
                                if (w.Resource.Amount == resIntens[k])
                                    w.ResID = r++;
                    }
                }
                List<int> resIdList = new List<int>();
                foreach (Work w in list)
                    resIdList.Add(w.ResID);
                resIdList.Sort();
                double sum = 0;
                foreach (int rs in resIdList)
                {
                    foreach (Work w in list)
                        if (w.ResID == rs)
                            sum += w.Resource.Amount;
                    if (sum > resMax)
                    {
                        foreach (Work w in list)
                            if (w.ResID >= rs)
                                w.timestart = segments[ind + 1];
                        return false;
                    }
                }      
                return false;
            }
        }

        public StatArray Run()
        {
            StatArray statistics = new StatArray();
            for (int iter = 0; iter < Iterations; iter++)
            {
                this.BuildDeterm();
                statistics.Res.AddElement(determ.getFullResources());
                statistics.PrjTime.AddElement(determ.getPrjTime());
                foreach (Work w in determ)
                {
                    statistics.CritWorks.AddElement(w.id);
                }
                Onchanged(iter+1);
            }
            statistics.PrjTime.Sort();
            statistics.Res.Sort();
            CritWorksDataSort(statistics);
            return statistics;
            
        }
        private void CritWorksDataSort(StatArray statistics)
        {
            this.original.RefreshWorkTime();
            double[] Xtmp = new double[statistics.CritWorks.XData.Count];
            double[] Ytmp = new double[statistics.CritWorks.YData.Count];
            statistics.CritWorks.XData.CopyTo(Xtmp);
            statistics.CritWorks.YData.CopyTo(Ytmp);
            statistics.CritWorks.XData.Sort(CritSortRule);

            for (int i = 0; i < statistics.CritWorks.XData.Count; i++)
            {
                for (int j = 0; j < Xtmp.Length; j++)
                {
                    if (statistics.CritWorks.XData[i] == Xtmp[j])
                    {
                        statistics.CritWorks.YData[i] = Ytmp[j];
                    }
                }
            }
        }

        private WorkList BuildDeterm()
        {
            this.stohastic = (WorkList)original.Clone();
            stohastic.Ranking();
            determ = new WorkList();
            int maxRank = stohastic.getMaxRank();
            for (int rank = 0; rank <= maxRank; rank++)
            {
                CalcGroups(rank);
                ChooseWorks();
            }
            for (int i = 0; i < determ.Count; i++ )
            {
                determ[i].Type = WorkType.Простая;
                determ.UpdateWork(determ[i]);
            }
            return determ;
        }

        private void ChooseWorks()
        {
            foreach (WorkGroup group in groups)
            {
                switch (group.workType)
                {
                    case WorkType.Простая:
                        determ.AddRange(group.works);
                        break;
                    case WorkType.ИЛИ:
                        determ.AddRange(ChooseORWorks(group.works));
                        break;
                    case WorkType.Искл_ИЛИ:
                        determ.Add(ChooseXORWorks(group.works));
                        break;
                    case WorkType.Возврат:
                        break;
                    default:
                        break;
                }                
            }
        }

        private Work ChooseXORWorks(WorkList works)
        {
            WorkList delworks = new WorkList();
            double[] probs = new double[works.Count+1];
            probs[0] = 0.0;

            for (int i = 0; i < works.Count; i++)
            {
                probs[i + 1] = probs[i] + works[i].Probability;
            }

            double val;
            while ((val = rnd.NextDouble()) == 0)
            {
            }
            int retID = 0;
            for (int i = 0; i < probs.Length-1; i++)
            {
                if (val <= probs[i + 1] && val > probs[i])
                {
                    retID = i;
                }
                else
                {
                    delworks.Add(works[i]);
                }
            }
            RemoveSpareWorks(delworks);
            return works[retID];
        }

        private WorkList ChooseORWorks(WorkList works)
        {
            WorkList output = new WorkList();
            WorkList delworks = new WorkList();
            foreach (Work work in works)
            {
                if (work.Probability >= rnd.NextDouble())
                {
                    output.Add(work);
                }
                else
                {
                    delworks.Add(work);
                }
            }
            RemoveSpareWorks(delworks);
            return output;
        }

        private void RemoveSpareWorks(WorkList delworks)
        {
            bool spare = false;
            foreach (Work w in delworks)
            {
                w.isMissed = true;
                stohastic.UpdateWork(w);
                spare = true;
            }
            
            while (spare)
            {
                spare = false;
                foreach (Work w in stohastic)
                {
                    if (!w.isMissed && w.isSpare())
                    {
                        w.isMissed = true;
                        stohastic.UpdateWork(w);
                        spare = true;
                        break;
                    }
                }
            }
            stohastic.DeleteMissedWorks();
        }

        private void CalcGroups(int rank)
        {
            
            currWorkSet.Clear();
            groups.Clear();
            currWorkSet.AddRange(stohastic.Where(work => work.rank == rank).OrderBy(work => work.id).Select(work => work));
            
            //группа SIMPLE работ
            WorkList sgl = new WorkList();
            sgl.AddRange(currWorkSet.Where(work=> work.Type == WorkType.Простая).Select(work => work).Select(work => work));
            WorkGroup simple_group = new WorkGroup(WorkType.Простая, sgl);
            groups.Add(simple_group);
            
            //группы OR работ
            WorkList ogl = new WorkList();
            ogl.AddRange(currWorkSet.Where(work => work.Type == WorkType.ИЛИ).Select(work => work).Select(work => work));
            BuildGroupByType(WorkType.ИЛИ, ogl);
            
            //группы XOR работ
            WorkList xgl = new WorkList();
            xgl.AddRange(currWorkSet.Where(work => work.Type == WorkType.Искл_ИЛИ).Select(work => work).Select(work => work));
            BuildGroupByType(WorkType.Искл_ИЛИ, xgl);
            
        }

        private void BuildGroupByType(WorkType type, WorkList works)
        {
            List<WorkGroup> groups = new List<WorkGroup>();
            if (works.Count != 0)
            {
                for (int i = 0; i < works.Count; i++)
                {
                    bool isgrouped = false;

                    foreach (WorkGroup gr in groups)
                    {
                        if (works[i].isOneGroup(gr.depInd) && !gr.works.Contains(works[i]))
                        {
                            gr.works.Add(works[i]);
                            isgrouped = true;
                        }
                    }

                    if (!isgrouped)
                    {
                        groups.Add(new WorkGroup(works[i]));
                    }
                }
            }
            this.groups.AddRange(groups);
        }
    }
}
